using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using WindowsPhoneEssentials.Diagnostics;
using WindowsPhoneMVC.ActionResults;
using WindowsPhoneMVC.Framework.Interfaces;
using WindowsPhoneMVC.Navigation;
using WindowsPhoneMVC.Navigation.Exceptions;

namespace WindowsPhoneMVC.Framework
{
    public class DefaultActionResolver : IActionResolver
    {
        public Func<Controller, ActionResult> ResolveControllerAction(Type controllerType, NavigationRequest navigationRequest)
        {
            using (TraceScope.Create(this, TraceLevel.Debug, ()=>string.Format("Resolving best matching action {1} on {0}", navigationRequest.ControllerName, navigationRequest.ActionName)))
            {
                var possibleActions = controllerType
                    .GetMethods()
                    .Where(m => NameMatches(navigationRequest, m))
                    .ToArray();

                if (!possibleActions.Any())
                    throw new ActionNotFoundException(navigationRequest.ActionName);

                return GetBestMatch(possibleActions, navigationRequest);
            }
        }

        private static bool NameMatches(NavigationRequest navigationRequest, MemberInfo m)
        {
            return DefaultViewLocator.RemoveSuffixes(m.Name) == DefaultViewLocator.RemoveSuffixes(navigationRequest.ActionName);
        }

        public Func<Controller, ActionResult> GetBestMatch(MethodInfo[] possibleActions, NavigationRequest navigationRequest)
        {
            if (possibleActions.Length == 1)
            {
                if (navigationRequest.Parameters.Count() == possibleActions[0].GetParameters().Length)
                    return c => possibleActions[0].Invoke(c, navigationRequest.Parameters.Select(p=>p.Value).ToArray()) as ActionResult;
            }

            var requestParameters = navigationRequest.Parameters.ToArray();

            foreach (var possibleAction in possibleActions.OrderByDescending(p => p.GetParameters().Length))
            {
                var methodParameters = possibleAction.GetParameters();
                if (methodParameters.Length > requestParameters.Length)
                    continue;

                var navigationParameters = navigationRequest.Parameters.ToArray();

                var methodArguments = new List<object>();
                for (var index = 0; index < methodParameters.Length; index++)
                {
                    var methodParameter = methodParameters[index];
                    var navigationParameter = navigationParameters[index];
                    var parameterValue = navigationParameter.Value;
                    if ((parameterValue == null && methodParameter.ParameterType.IsValueType) ||
                        (parameterValue != null && !methodParameter.ParameterType.IsAssignableFrom(parameterValue.GetType())))
                    {
                        break;
                    }
                    methodArguments.Add(parameterValue);
                }

                //We didn't match all the method parameters
                if (methodArguments.Count != methodParameters.Length)
                    continue;

                return c => possibleAction.Invoke(c, methodArguments.ToArray()) as ActionResult;
            }

            Trace.WriteError(this, ()=>string.Format(
                "Failed to find a matching action called {0} on controller {1}", 
                navigationRequest.ActionName, navigationRequest.ControllerName));
            throw new ActionNotFoundException(navigationRequest.ActionName);
        }
    }
}